package mgt

import gateway.mgt.api.ApiConstantParam
import geb.spock.GebSpec
import grails.async.PromiseList
import grails.plugins.rest.client.RestBuilder
import grails.testing.mixin.integration.Integration
import grails.transaction.Rollback
import mgt.pages.*
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.HttpStatus
import org.springframework.util.LinkedMultiValueMap
import spock.lang.Stepwise

/**
 * See http://www.gebish.org/manual/current/ for more instructions
 */
@Stepwise
@Rollback
@Integration(applicationClass = mgt.Application)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class ApiMgtDemoSpec extends GebSpec {

    static final AuthSrvHost = "http://localhost:6543"
    static final GatewayHost = "http://localhost:8765"

    private static String SpecialUserClientId

    static final RestBuilder rest = new RestBuilder()

    def setup() {
    }

    def cleanup() {
    }

    void "should access webapp"() {
        when: "The home page is visited"
        go '/'

        then:
        at IndexPage
    }

    def "you must login before click any menus, and there are no api group"() {
        when:
        $("a.list", 0).click()

        then: "need to login"
        waitFor {
            at LoginPage
        }

        when: "enter username and password"
        ((LoginPage) browser.page).login("user", "123")

        then: "login success and forward ApiGroupListPage"
        waitFor {
            at ApiGroupListPage
        }

        and:
        ((ApiGroupListPage) browser.page).numberOfApiGroups == 0
    }

    def "should create api group"() {
        given:
        def createPage = to ApiGroupCreatePage

        when:
        def showPage = createPage.createApiGroup('GATEWAY-CORE')

        then:
        showPage.apiGroupName == 'GATEWAY-CORE'
    }

    def "there are no api definition"() {
        when:
        def listPage = to ApiDefinitionListPage

        then:
        listPage.numberOfApiDefinitions == 0
    }

    def "should create api definition"() {
        given:
        def createPage = to ApiDefinitionCreatePage

        when:
        def showPage = createPage.createApiDefinition('GATEWAY-CORE', 'proxy-infoq-articles', 'InfoQ代理，后端超时阈值易熔断', "/articles/**", "http://www.infoq.com", "/", 1L)

        then:
        showPage.apiDefinitionName == 'proxy-infoq-articles'
        showPage.apiDefinitionRequestPath == '/articles/**'
        showPage.apiDefinitionBackendUri == 'http://www.infoq.com'
    }

    def "there are no api constant param"() {
        when:
        def listPage = to ApiConstantParamListPage

        then:
        listPage.numberOfApiConstantParams == 0
    }

    def "should create api constant param"() {
        given:
        def createPage = to ApiConstantParamCreatePage

        when:
        def showPage = createPage.createApiConstantParam(1, 1, ApiConstantParam.Type.String, ApiConstantParam.Scope.Path, "area", "cn", "测试Path常量")

        then:
        showPage.apiConstantParamName == 'area'
    }

    def "should release api by group without auth"() {

        when:
        def appGroupShowPage = to ApiGroupShowPage, 1

        and:
        appGroupShowPage.release()

        then:
        waitFor(15) {
            downloadText().contains("success")
        }
    }

    def "validate api for unauthorized"() {
        given:
        /*
        https://www.infoq.com/articles/continuous-delivery-teamplay
        http://www.infoq.com/cn/articles/continuous-delivery-teamplay
         */
        def url = GatewayHost + "/articles/continuous-delivery-teamplay"

        when: "call target url without app auth"
        def resp2 = rest.get(url)

        then:
        resp2.statusCode == HttpStatus.UNAUTHORIZED
    }

    def "should create app auth and authorized api"() {
        given:
        def createPage = to ApiAuthCreatePage

        when:
        def showPage = createPage.createApiAuth("authDemo", "auth demo", 1L)

        then:
        showPage.appName == 'authDemo'

        and:
        showPage.apiDefinitionsCount == 1

        when:
        def apiShowPage = to(ApiDefinitionShowPage, 1)

        then:
        apiShowPage.apiAuthCount == 1
    }

    def "should release api by group with auth"() {
        when:
        def appGroupShowPage = to ApiGroupShowPage, 1

        and:
        appGroupShowPage.release()

        then:
        waitFor {
            downloadText().contains("success")
        }
    }

    def "validate api for auth but circuit breaker open"() {
        given:
        /*
        https://www.infoq.com/articles/continuous-delivery-teamplay
        http://www.infoq.com/cn/articles/continuous-delivery-teamplay
         */
        def url = GatewayHost + "/articles/continuous-delivery-teamplay"

        when:
        def showPage = to(ApiAuthShowPage, 1)

        and:
        def form = new LinkedMultiValueMap<String, String>()
        form.add("grant_type", "client_credentials")
        def resp2 = rest.post(AuthSrvHost + "/oauth/token") {
            auth(showPage.clientId, showPage.clientSecret)
            accept("application/json")
            contentType("application/x-www-form-urlencoded")
            body(form)
        }.json

        and:
        def resp3 = rest.get(url) {
            header "Authorization", "Bearer ${resp2.access_token}"
        }

        then:
        resp3.statusCode == HttpStatus.OK
        resp3.json.state == "offline" //hard-code hystrix rollback
    }

    def "should edit api definition"() {
        given:
        def apiDefinitionId = 1

        when:
        def editPage = to(ApiDefinitionEditPage, apiDefinitionId)

        and:
        def showPage = editPage.editApiDefinition(6000L, "恢复正常后端超时阈值，使服务可以正常被调用")

        then:
        showPage.apiDefinitionBackendTimeout == "6,000"
    }

    def "should republish api by group"() {
        when:
        def appGroupShowPage = to ApiGroupShowPage, 1

        and:
        appGroupShowPage.release()

        then:
        waitFor {
            downloadText().contains("success")
        }

    }

    def "validate api for auth and circuit breaker close"() {
        given:
        /*
        https://www.infoq.com/articles/continuous-delivery-teamplay
        http://www.infoq.com/cn/articles/continuous-delivery-teamplay
         */
        def url = GatewayHost + "/articles/continuous-delivery-teamplay"

        when:
        def showPage = to(ApiAuthShowPage, 1)

        and:
        def form = new LinkedMultiValueMap<String, String>()
        form.add("grant_type", "client_credentials")
        def resp2 = rest.post(AuthSrvHost + "/oauth/token") {
            auth(showPage.clientId, showPage.clientSecret)
            accept("application/json")
            contentType("application/x-www-form-urlencoded")
            body(form)
        }.json

        and:
        def resp3 = rest.get(url) {
            header "Authorization", "Bearer $resp2.access_token"
        }

        then:
        (resp3.statusCode == HttpStatus.OK) || (resp3.statusCode == HttpStatus.MOVED_PERMANENTLY)
        resp3.text.length() > 0
    }

    def "should create rate limit strategy and binding api"() {
        given:
        def createPage = to RateLimitStrategyCreatePage

        when:
        def showPage = createPage.createRateLimitStrategy(1, "demo-rate-limit", 1, 1, "测试限流策略")

        then:
        showPage.apiLimit == '1'
    }

    def "should release api by group with rate limited"() {
        when:
        def appGroupShowPage = to ApiGroupShowPage, 1

        and:
        appGroupShowPage.release()

        then:
        waitFor {
            downloadText().contains("success")
        }
    }

    def "validate api for rate limited"() {
        when:
        def showPage = to(ApiAuthShowPage, 1)

        and:
        def form = new LinkedMultiValueMap<String, String>()
        form.add("grant_type", "client_credentials")
        def resp2 = rest.post(AuthSrvHost + "/oauth/token") {
            auth(showPage.clientId, showPage.clientSecret)
            accept("application/json")
            contentType("application/x-www-form-urlencoded")
            body(form)
        }.json

        and:
        def loopCount = 9
        def url = GatewayHost + "/articles/continuous-delivery-teamplay"
        def rest = new RestBuilder()
        def tasks = new PromiseList()

        while (loopCount > 0) {
            tasks << {
                final idx = loopCount
                def statusCode = rest.get(url) {
                    header "Authorization", "Bearer ${resp2.access_token}"
                }.statusCode
                println "[rate limit test]loopCount:$idx>>statusCode:$statusCode"
                return statusCode
            }
            loopCount--
        }
        def taskResult = tasks.get()

        then:
        taskResult.findAll {
            it == HttpStatus.TOO_MANY_REQUESTS
        }.size() == 8

        and:
        taskResult.findAll {
            it == HttpStatus.OK
        }.size() == 1
    }

    def "should create app auth for SpecialUser and authorized api"() {
        given:
        def createPage = to ApiAuthCreatePage

        when:
        def showPage = createPage.createApiAuth("SpecialUser", "Special User Rate Limit", 1L)
        SpecialUserClientId = showPage.clientId

        then:
        showPage.appName == 'SpecialUser'

        and:
        showPage.apiDefinitionsCount == 1

        when:
        def apiShowPage = to(ApiDefinitionShowPage, 1)

        then:
        apiShowPage.apiAuthCount == 2
    }

    def "should create special user rate limit strategy"() {
        given:
        def threshold = 2L
        def createPage = to SpecialUserCreatePage

        when:
        def showPage = createPage.createSpecialUser(1, SpecialUserClientId, threshold)

        then:
        showPage.specialUserClientId == SpecialUserClientId
        showPage.specialUserThreshold == threshold.toString()
    }

    def "should release api by group with special user rate limited"() {
        when:
        def appGroupShowPage = to ApiGroupShowPage, 1

        and:
        appGroupShowPage.release()

        then:
        waitFor {
            downloadText().contains("success")
        }
    }

    def "validate api for special user rate limited"() {
        given:
        def loopCount = 9
        def url = GatewayHost + "/articles/continuous-delivery-teamplay"
        def rest = new RestBuilder()
        def tasks = new PromiseList()

        when:
        def showPage = to(ApiAuthShowPage, 2)

        and:
        def form = new LinkedMultiValueMap<String, String>()
        form.add("grant_type", "client_credentials")
        def resp2 = rest.post(AuthSrvHost + "/oauth/token") {
            auth(showPage.clientId, showPage.clientSecret)
            accept("application/json")
            contentType("application/x-www-form-urlencoded")
            body(form)
        }.json

        and:
        while (loopCount > 0) {
            tasks << {
                final idx = loopCount
                def statusCode = rest.get(url) {
                    header "Authorization", "Bearer ${resp2.access_token}"
                }.statusCode

                println "[rate limit test]loopCount:$idx>>statusCode:$statusCode"

                return statusCode
            }
            loopCount--
        }
        def taskResult = tasks.get()

        then:
        taskResult.findAll {
            it == HttpStatus.TOO_MANY_REQUESTS
        }.size() > 0

        and:
        taskResult.findAll {
            it == HttpStatus.OK
        }.size() == 2
    }
}
